/**
 * Copyright (C) 2009 Stefan Maassen
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package de.indisopht.guice.groovy.spi;

import groovy.lang.GroovyClassLoader;
import groovy.lang.Script;

import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;

import org.osgi.framework.Bundle;

import com.google.inject.AbstractModule;
import com.google.inject.Binder;
import com.google.inject.Key;
import com.google.inject.Module;
import com.google.inject.spi.Elements;
import com.google.inject.spi.ModuleWriter;

import de.indisopht.guice.groovy.spi.internal.BridgedClassLoader;
import de.indisopht.guice.groovy.spi.internal.GroovyClassloaderFactory;
import de.indisopht.guice.groovy.spi.internal.GroovyModuleRewriter;
import de.indisopht.guice.groovy.spi.internal.GroovyProvider;
import de.indisopht.guice.groovy.spi.internal.annotations.GroovyGuiceInternal;
import de.indisopht.guice.groovy.spi.internal.annotations.GroovyGuiceInternalFactory;

public class GroovyModuleBuilder {

    public static GroovyModuleBuilderInstance create(Module... modules) {
        GroovyModuleBuilderInstance result = new GroovyModuleBuilderInstance();
        result.modules.addAll(Arrays.asList(modules));
        return result;
    }
    
    public static GroovyModuleBuilderInstance createWithEmptyModule() {
        GroovyModuleBuilderInstance result = new GroovyModuleBuilderInstance();
        result.modules.add(new Module() {
            public void configure(Binder binder) {
                // we need at least one binding to get the ModuleWriter working
                binder.bindConstant().annotatedWith(GroovyGuiceInternalFactory.create()).to("");
            }
        });
        return result;
    }

    public static class GroovyModuleBuilderInstance {

        private Collection<Module> modules = new ArrayList<Module>();
        private GroovyClassloaderFactory parameters = new GroovyClassloaderFactory();
        Map<Key<Script>, String> scripts = new HashMap<Key<Script>, String>();
        private Bundle bundle;
        
        public GroovyModuleBuilderInstance addClasspath(String path) {
            parameters.getClassPath().setClasspath(path);
            return this;
        }

        /**
         * please note:
         * if peaberry is used for integrating OSGi, configuring the bundle is optional
         * 
         * @param b the bundle
         * @return this
         */
        public GroovyModuleBuilderInstance forBundle(Bundle b) {
            this.bundle=b;
            return this;
        }
        
        public GroovyModuleBuilderInstance resolveClasses(Boolean b) {
            parameters.setResolve(b);
            return this;
        }

        public GroovyModuleBuilderInstance resolveClasses(String b) {
            parameters.setResolve(Boolean.parseBoolean(b));
            return this;
        }

        public GroovyModuleBuilderInstance useCodeBase(String base) {
            parameters.setCodeBase(base);
            return this;
        }
        
        public GroovyModuleBuilderInstance enableRecompilation() {
            parameters.setRecompile(true);
            return this;
        }
        
        public GroovyModuleBuilderInstance disableRecompilation() {
            parameters.setRecompile(false);
            return this;
        }

        public ScriptBindConfig script(String script) {
            return new ScriptBindConfig(this, script);
        }
        
        public Module build() {
            Module unboundProviders=new AbstractModule() {

                @Override
                protected void configure() {
                    parameters.setBridgedClassloader(new BridgedClassLoader(Thread.currentThread().getContextClassLoader(), bundle));
                    bind(GroovyClassLoader.class).annotatedWith(GroovyGuiceInternal.class).toInstance(parameters.getGroovyClassLoader());
                    bind(GroovyClassloaderFactory.class).toInstance(parameters);
                    for (Entry<Key<Script>, String> currentEntry : scripts.entrySet()) {
                        bind(currentEntry.getKey()).toProvider(new GroovyProvider<Script>(Script.class, parameters, currentEntry.getValue()));
                    }
                }
            };
            modules.add(unboundProviders);
            ModuleWriter rewriter = new GroovyModuleRewriter();
            Module result = rewriter.create(Elements.getElements(modules));
            return result;
        }
    }
    
    public static class ScriptBindConfig  {
        
        private final GroovyModuleBuilderInstance parent;
        private final String script;
        private Annotation annotation=null;
        
        public ScriptBindConfig(GroovyModuleBuilderInstance parent, String script) {
            this.parent=parent;
            this.script=script;
        }
        
        public ScriptBindConfig withAnnotation(Annotation annotation) {
            this.annotation=annotation;
            return this;
        }
        
        public GroovyModuleBuilderInstance bind() {
            if (annotation!=null) {
                parent.scripts.put(Key.get(Script.class, annotation), script);
            } else {
                parent.scripts.put(Key.get(Script.class), script);
            }
            return parent;
        }
    }
}
